package czolgisci.map;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import javax.imageio.ImageIO;
import utils.geom.Direction;
import utils.time.IClockListener;

import czolgisci.display.ISpritesContainer;
import czolgisci.events.TickEvent;
import czolgisci.tiles.AMovableTile;
import czolgisci.tiles.ATile;
import czolgisci.tiles.Bullet;
import czolgisci.tiles.ITilesFeedbackListener;
import czolgisci.tiles.Tank;
import czolgisci.tiles.TileType;
import czolgisci.tiles.Wall;
import czolgisci.tiles.factory.ITileFactory;

/**
 * Klasa zarządzająca mapami gry.
 * 
 * Agreguje obiekt klasy ArrayList (używa go jako kontener na obrazki
 * reprezentujące różne typy kafelków, np. czołg lub ścianę).
 * 
 * Oczekuje sygnałów od zegara (implementuje interfejs IClockListener)
 * 
 * @author pillow
 * 
 */
public class MapManager implements ISpritesContainer, IClockListener,
		ITilesFeedbackListener
{
	private ITileFactory tileKit;
	private ATile[][] tilesContainer;
	private int width;
	private int height;
	private int HorizontalTilesNum;
	private int VerticalTilesNum;
	private ArrayList<AMovableTile> tanks;
	private ArrayList<Point> tanksDefaultPositions;

	/**
	 * Konstruktor klasy
	 * 
	 * @param mapObject
	 *            obiekt mapy
	 * @param tileFactory
	 *            wybrana konkretna fabryka kafelków
	 */
	public MapManager(Map mapObject, ITileFactory tileFactory)
	{
		tileKit = tileFactory;
		HorizontalTilesNum = mapObject.width;
		VerticalTilesNum = mapObject.height;
		width = HorizontalTilesNum * tileKit.getTileWidth();
		height = VerticalTilesNum * tileKit.getTileHeight();

		tilesContainer = new ATile[VerticalTilesNum][HorizontalTilesNum];
		tanks = new ArrayList<AMovableTile>();
		tanksDefaultPositions = new ArrayList<Point>();
		TileType tileType;
		ATile tile;
		AMovableTile tank;

		for (int j = 0; j < VerticalTilesNum; j++)
		{
			for (int i = 0; i < HorizontalTilesNum; i++)
			{
				try
				{
					tileType = MapCodeInterpreter
							.decodeTileType(mapObject.map[j][i]);
					tile = tileKit.makeTile(tileType, new Point(i
							* tileKit.getTileWidth(), j
							* tileKit.getTileHeight()));
					tilesContainer[j][i] = tile;

					if (tileType == TileType.TANK)
					{
						tank = (AMovableTile) tile;
						tanks.add(tank);
						System.out.println(tank.getPosition());
						tanksDefaultPositions.add((Point) tank.getPosition().clone());
						tank.registerFeedbackListener(this);
					}
				} catch (IllegalArgumentException e)
				{
					//jeśli znaleziono na mapie spację
				} catch (IOException e)
				{
					e.printStackTrace();
				}
			}
		}
	}

	public ArrayList<AMovableTile> getTanks()
	{
		return tanks;
	}

	/**
	 * @return ilość kafelków w poziomie
	 */
	public int getVerticalTilesNum()
	{
		return VerticalTilesNum;
	}

	/**
	 * @return ilość kafelków w pionie
	 */
	public int getHorizontalTilesNum()
	{
		return HorizontalTilesNum;
	}

	/**
	 * @return ilość pikseli w poziomie
	 */
	public int getWidth()
	{
		return width;
	}

	/**
	 * @return ilość pikseli w poziomie
	 */
	public int getHeight()
	{
		return height;
	}

	/**
	 * @return obrazek tła dla aktualnej mapy
	 */
	public BufferedImage getBackground() throws IOException
	{
		return (BufferedImage) ImageIO.read(new File(
				"res/img/backgrounds/default.png"));
	}

	/**
	 * Niszczy aktualną mapę
	 */
	public void destroyCurrentMap()
	{

	}

	/**
	 * Ładuje mapę
	 * 
	 * @param mapObject
	 *            obiekt mapy
	 */
	public void loadMap(Map mapObject)
	{

	}

	/**
	 * Pobiera sąsiedni kafelek w danym kierunku
	 * 
	 * @param from
	 *            początkowy kafelek
	 * @param direction
	 *            kierunek, w którym sprawdzany jest sąsiad
	 * @return sąsiedni kafelek
	 */
	public ATile getNeighbourTile(ATile from, Direction direction) throws IllegalArgumentException, ArrayIndexOutOfBoundsException
	{
		Point position = getIJ(from);

		switch (direction)
		{
			case NORTH:
				return tilesContainer[position.y-1][position.x];
			case EAST:
				return tilesContainer[position.y][position.x+1];
			case SOUTH:
				return tilesContainer[position.y+1][position.x];
			case WEST:
				return tilesContainer[position.y][position.x-1];
			default:
				throw new IllegalArgumentException();
		}
	}

	/**
	 * Pobiera kafelek z pozycji XY (piksele)
	 * 
	 * @param x
	 * @param y
	 * @return wskazany kafelek
	 */
	public ATile getTileFromXY(int x, int y)
	{
		return null;
	}

	/**
	 * Pobiera kafelek z pozycji IJ
	 * 
	 * @param i
	 * @param j
	 * @return wskazany kafelek
	 */
	public ATile getTileFromIJ(int i, int j)
	{
		return tilesContainer[j][i];
	}

	/**
	 * Umożliwia wypełnienie pola mapy obiektem (kafelkiem) z zewnątrz.
	 * 
	 * @param x
	 *            pozycja x (px)
	 * @param y
	 *            pozycja y (px)
	 */
	public void setTileAtXY(int x, int y)
	{

	}

	/**
	 * Umożliwia wypełnienie pola mapy obiektem (kafelkiem) z zewnątrz.
	 * 
	 * @param i
	 *            (współrzędna tablicy)
	 * @param j
	 *            (współrzędna tablicy)
	 */
	public void setTileAtIJ(int i, int j)
	{

	}

	/**
	 * Sprawdzenie kolizji i podjęcie stosownych kroków
	 * 
	 * @param bullet
	 *            czołg, który się przesunął
	 * @see czolgisci.tiles.ITilesFeedbackListener#onTankMove(czolgisci.tiles.Tank)
	 */
	public void onTankMove(Tank tank)
	{
		Rectangle tankRectangle = tank.getArea();
		Point tankNewPosition = getIJ(tank);
		Rectangle intersection;
		ATile neighbour;
		int i;

		for (Direction direction : Direction.values())
		{
			try
			{
				neighbour = getNeighbourTile(tank, direction);
			} catch (ArrayIndexOutOfBoundsException e)
			{
				neighbour = tilesContainer[tankNewPosition.y][tankNewPosition.x];
			}

			if (neighbour != null && neighbour != tank)
			{
				intersection = tankRectangle.intersection(neighbour.getArea());
				
				if (!intersection.isEmpty())
				{
					if (neighbour instanceof Wall)
					{
						switch (tank.getDirection().getReversedDirection())
						{
							case NORTH:
								tank.setY(neighbour.getPosition().y - tank.getSize().height);
								break;
							case EAST:
								tank.setX(neighbour.getPosition().x + tank.getSize().width);
								break;
							case SOUTH:
								tank.setY(neighbour.getPosition().y + tank.getSize().height);
								break;
							case WEST:
								tank.setX(neighbour.getPosition().x - tank.getSize().width);
								break;
						}

						tilesContainer[getIJ(tank).y][getIJ(tank).x] = tank;
					}
					else if (neighbour instanceof Bullet || neighbour instanceof Tank)
					{
						if (!(tilesContainer[tankNewPosition.y][tankNewPosition.x] instanceof Wall))
							tilesContainer[tankNewPosition.y][tankNewPosition.x] = null;
						
						Point neighbourPosition = getIJ(neighbour);
						
						if (!(tilesContainer[neighbourPosition.y][neighbourPosition.x] instanceof Wall))
							tilesContainer[neighbourPosition.y][neighbourPosition.x] = null;
						
						i = 0;
						for (AMovableTile tmpTank : tanks)
						{
							System.out.println(getIJfromXY(tanksDefaultPositions.get(i)));
							tmpTank.setPosition(tanksDefaultPositions.get(i));
							tilesContainer[getIJ(tmpTank).y][getIJ(tmpTank).x] = tmpTank;
							i++;
						}

						break;
					}


				}
			}
		}
	}

	/**
	 * Sprawdzenie kolizji i podjęcie stosownych kroków
	 * 
	 * @param bullet
	 *            nabój, który się przesunął
	 * @see czolgisci.tiles.ITilesFeedbackListener#onBulletMove(czolgisci.tiles.Bullet)
	 */
	public void onBulletMove(Bullet bullet)
	{
		
	}

	/**
	 * Zwraca iterator do kontenera z graficznymi reprezentacjami obiektów
	 * kafelkowych.
	 * 
	 * @see czolgisci.display.IImageContainer#iterator()
	 */
	public ISpritesIterator iterator()
	{
		return new MapManagerIterator(this);
	}

	/**
	 * Metoda uruchamiana po odebraniu sygnału zegara. Standardowo wywołuje
	 * funkcję renderującą mapę
	 * 
	 * @see utils.time.IClockListener#onTick(czolgisci.events.TickEvent)
	 */
	public void onTick(TickEvent e)
	{

	}

	public void onShoot(Tank tank)
	{
		// TODO Auto-generated method stub

	}

	/**
	 * @param tile
	 * @return kafelek, na którym znajduje się obiekt
	 */
	private Point getIJ(ATile tile)
	{
		return getIJfromXY(tile.getPosition());
	}

	/**
	 * @param tile
	 * @return kafelek, na którym znajduje się obiekt
	 */
	private Point getIJfromXY(Point position)
	{
		int i = Math.round((position.x / tileKit.getTileWidth()));
		int j = Math.round((position.y / tileKit.getTileHeight()));
		return new Point(i, j);
	}

	/**
	 * @param position pozycja kafelka
	 * @return pozycja w pikselach lewego górnego rogu kafelka
	 */
	private Point getXYfromIJ(Point position)
	{
		return new Point(position.x * tileKit.getTileWidth(), position.y
				* tileKit.getTileHeight());
	}

	/**
	 * @param tile
	 *            przesunięty obiekt
	 * @param j
	 *            aktualna współrzędna j w kafelkach
	 * @param i
	 *            aktualna współrzędna i w kafelkach
	 */
	private void removeFromPreviousField(AMovableTile tile)
	{
		Point position = getIJ(tile);

		switch (tile.getDirection().getReversedDirection())
		{
			case NORTH:
				tilesContainer[position.y-1][position.x] = null;
				break;
			case EAST:
				tilesContainer[position.y][position.x+1] = null;
				break;
			case SOUTH:
				tilesContainer[position.y+1][position.x] = null;
				break;
			case WEST:
				tilesContainer[position.y][position.x-1] = null;
				break;
			default:
				throw new IllegalArgumentException();
		}
	}
}
